package com.shadow.demo.excel.demo.v3;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shadow.demo.common.util.ValidationUtils;
import com.shadow.demo.excel.easy.excel.MyExcelException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;

import javax.validation.ConstraintViolation;
import java.util.*;
import java.util.function.Function;

@Slf4j
public class Import3Listener<S extends ServiceImpl<M, T>, M extends BaseMapper<T>, T, V> implements ReadListener<V> {

    /* 每隔x条存储数据库然后清理list, 方便内存回收 */
    private static final int BATCH_COUNT = 3;

    /* service */
    private final S service;

    /* excelVO转换为entity */
    private final Function<V, T> convert;

    /* 处理的条数 */
    private int index = 0;

    /* 表头最大行号 */
    private int maxHeadIndex = 1;

    /* 缓存的数据 */
    private final List<T> cacheList = new ArrayList<>(BATCH_COUNT);

    /* 字段校验结果 */
    private final Map<String, String> errorMsg = new HashMap<>();

    public Import3Listener(final S service, final Function<V, T> convert) {
        this.service = service;
        this.convert = convert;
    }

    /**
     * 当发生异常时进入
     *
     * @param exception 异常
     * @param context   excel内容
     * @throws Exception 异常
     */
    @Override
    public void onException(final Exception exception, final AnalysisContext context) throws Exception {
        log.info("enter onException");
        throw exception;
    }

    /**
     * 校验表头时进入
     *
     * @param headMap 表头
     * @param context excel内容
     */
    @Override
    public void invokeHead(final Map<Integer, ReadCellData<?>> headMap, final AnalysisContext context) {
        log.info("enter invokeHead");
        Integer headIndex = context.readRowHolder().getRowIndex();
        if (headIndex != null) {
            this.maxHeadIndex = headIndex;
        }
    }

    /**
     * 处理一整行数据
     * @param data 一行数据
     *             等同于 {@link AnalysisContext#readRowHolder()}
     * @param context excel内容
     */
    @Override
    public void invoke(final V data, final AnalysisContext context) {
        Boolean checked = check(context.readRowHolder().getRowIndex(), data);
        if (Boolean.TRUE.equals(checked)) {
            T apply = convert.apply(data);
            cacheList.add(apply);
        }
        index++;
        // 达到BATCH_COUNT了
        // 需要去存储一次数据库
        // 防止数据几万条数据在内存, 容易OOM
        if (index >= BATCH_COUNT) {
            submit();
        }
    }

    /**
     * 处理完了所有数据之后
     * */
    @Override
    public void doAfterAllAnalysed(final AnalysisContext context) {
        if (!cacheList.isEmpty()) {
            submit();
        }
        log.info("success");
    }

    /**
     * 校验
     */
    private Boolean check(final Integer rowNum, final V vo) {
        Set<ConstraintViolation<V>> violationSet = ValidationUtils.getValidator().validate(vo);
        if (CollectionUtils.isNotEmpty(violationSet)) {
            violationSet.forEach(x ->
                    errorMsg.put((rowNum + maxHeadIndex) + x.getPropertyPath().toString(), x.getMessage())
            );
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * 提交数据
     * 存储进数据库
     */
    private void submit() {
        if (!errorMsg.isEmpty()) {
            throw new MyExcelException(formatCheckResult(errorMsg));
        }
        boolean r = service.saveBatch(cacheList);
        if (r) {
            log.info("存储数据库成功！");
        }
        cacheList.clear();
    }

    private String formatCheckResult(final Map<?, ?> map) {
        if (MapUtils.isEmpty(map)) {
            return null;
        }
        StringBuilder stringBuilder = new StringBuilder();
        map.forEach((k, v) -> stringBuilder.append(k)
                .append(": ")
                .append(v)
                .append(", ")
        );
        return stringBuilder.substring(0, stringBuilder.length() - 2);
    }
}
